import { isApiStep, isCronStep, isEventStep } from '../guards'
import type { Printer } from '../printer'
import { schemaToJsonSchema } from '../schema-utils'
import type { Emit, Step } from '../types'
import type { Stream } from '../types-stream'
import { generateTypeFromSchema } from './generate-type-from-schema'
import { generateTypesFromResponse } from './generate-types-from-response'
import { mergeSchemas } from './merge-schemas'
import type { JsonSchema } from './schema.types'

type HandlersMap = Record<string, { type: string; generics: string[] }>
type StreamsMap = Record<string, string>

export const generateTypesString = (
  handlers: HandlersMap,
  streams: StreamsMap,
  streamAuthContextType?: string,
): string => {
  return `/**
 * Automatically generated types for motia
 * Do NOT edit this file manually.
 * 
 * Consider adding this file to .prettierignore and eslint ignore.
 */
import { EventHandler, ApiRouteHandler, ApiResponse, MotiaStream, CronHandler } from 'motia'

declare module 'motia' {
  interface FlowContextStateStreams {
    ${Object.entries(streams)
      .map(([key, value]) => `'${key}': MotiaStream<${value}>`)
      .join('\n    ')
      .trim()}
  }

  interface Handlers {
    ${Object.entries(handlers)
      .map(([key, { type, generics }]) => `'${key}': ${type}<${generics.join(', ')}>`)
      .join('\n    ')
      .trim()}
  }
    ${streamAuthContextType ? `interface StreamAuthContext ${streamAuthContextType}` : ''}
}`
}

export const generateTypesFromSteps = (steps: Step[], printer: Printer): HandlersMap => {
  const handlers: HandlersMap = {}
  const topics: Record<string, string> = {}
  const topicsSchemas: Record<string, JsonSchema> = {}
  const topicsSteps: Record<string, Step[]> = {}
  const topicIsFifo: Record<string, boolean> = {}

  for (const step of steps) {
    if (isEventStep(step)) {
      if (!step.config.input) {
        for (const topic of step.config.subscribes) {
          if (!topics[topic]) {
            topics[topic] = 'never'
          }
        }
        continue
      }

      for (const topic of step.config.subscribes) {
        const existingSchema = topicsSchemas[topic]

        topicsSteps[topic] = topicsSteps[topic] ?? []
        topicsSteps[topic].push(step)

        const queueType = step.config.infrastructure?.queue?.type
        if (queueType === 'fifo') {
          topicIsFifo[topic] = true
        }

        try {
          const input = step.config.input
          const schema = existingSchema
            ? mergeSchemas(existingSchema, input)
            : (schemaToJsonSchema(input) ?? (input as JsonSchema))
          topics[topic] = generateTypeFromSchema(schema)
          topicsSchemas[topic] = schema
        } catch (error) {
          printer.printInvalidSchema(topic, topicsSteps[topic])
          topics[topic] = 'never'
        }
      }
    }
  }

  const generateEmitData = (emit: Emit[], step: Step): string => {
    const emits = emit
      .reduce((acc, emit) => {
        const topic = typeof emit === 'string' ? emit : emit.topic
        const topicType = topics[topic]

        if (topicType) {
          const isFifo = topicIsFifo[topic]
          if (isFifo) {
            acc.push(`{ topic: '${topic.replace(/'/g, "\\'")}'; data: ${topicType}; messageGroupId: string }`)
          } else {
            acc.push(`{ topic: '${topic.replace(/'/g, "\\'")}'; data: ${topicType} }`)
          }
        } else {
          printer.printInvalidEmitConfiguration(step, topic)
        }

        return acc
      }, [] as string[])
      .join(' | ')

    return emits.length === 0 ? 'never' : emits
  }

  for (const step of steps) {
    const emits = 'emits' in step.config ? generateEmitData(step.config.emits, step) : 'never'

    if (isEventStep(step)) {
      const input = step.config.input ? generateTypeFromSchema(step.config.input as never as JsonSchema) : 'never'
      handlers[step.config.name] = { type: 'EventHandler', generics: [input, emits] }
    } else if (isApiStep(step)) {
      const input = step.config.bodySchema
        ? generateTypeFromSchema(step.config.bodySchema as never as JsonSchema)
        : 'Record<string, unknown>'
      const result = step.config.responseSchema
        ? generateTypesFromResponse(step.config.responseSchema as never as Record<number, JsonSchema>)
        : 'unknown'
      handlers[step.config.name] = { type: 'ApiRouteHandler', generics: [input, result, emits] }
    } else if (isCronStep(step)) {
      handlers[step.config.name] = { type: 'CronHandler', generics: [emits] }
    }
  }

  return handlers
}

export const generateTypesFromStreams = (streams: Record<string, Stream>): StreamsMap => {
  return Object.entries(streams).reduce((acc, [key, stream]) => {
    if (!stream.hidden) {
      acc[key] = generateTypeFromSchema(stream.config.schema as unknown as JsonSchema)
    }
    return acc
  }, {} as StreamsMap)
}
